/*********************************************************************************
 *      Copyright:  (C) 2022 Li Yanting<liyanting2@163.com>
 *                  All rights reserved.
 *
 *       Filename:  serial.c
 *    Description:  This file 
 *                 
 *        Version:  1.0.0(05/06/2022)
 *         Author:  Li Yanting <liyanting2@163.com>
 *      ChangeLog:  1, Release initial version on "05/06/2022 08:15:09"
 *                 
 ********************************************************************************/
#include "serial.h"

int serial_open(attr_ctx_t *attr)
{
    if( NULL == attr )
    {
        log_error("function %s invalid parameter\n", __func__);
        return -1;
    }

    /* open serial */
    attr->fd = open(attr->serial_name, O_RDWR | O_NOCTTY | O_NONBLOCK);
    if( attr->fd < 0 )
    {
        log_error("function %s open %s failed: %s\n", __func__, attr->serial_name, strerror(errno));
        return -2;
    }
    
    /* Check whether the serial is blocked */
    if( (fcntl(attr->fd, F_SETFL, 0)) < 0 )
    {
        log_error("function: %s fcntl check failure.\n", __func__);
        return -3;
    }

    /*Check whether it is a terminal device*/
    if(0 == isatty(attr->fd))  
    {
        log_error("%s[%d] is not a terminal equipment.\n", attr->serial_name, attr->fd);
        return -4;
    }

    return 0;
}

int serial_close(attr_ctx_t *attr)
{
    if( NULL == attr )
    {
        log_error("function %s invalid parameter\n", __func__);
        return -1;
    }
    
    /* TCIOFLUSH: Clear input / output queue*/
    if( tcflush(attr->fd, TCIOFLUSH) < 0 )
    {
        log_error("function %s tcflush failure: %s.\n", __func__, strerror(errno));
        return -2;
    }

    /* Set the serial port to the original attribute  TCSANOW: 立即生效*/
    if( tcsetattr(attr->fd, TCSANOW, &(attr->original_termios)) < 0 )
    {
        log_error("function %s set original termios failure: %s.\n", __func__, strerror(errno));
        return -3;
    }

    if( close(attr->fd) < 0 )
    {
        log_error("close %s failure: %s.\n", __func__, strerror(errno));
        return -4;
    }

    free(attr);
    attr = NULL;

    return 0;
}

int serial_init(attr_ctx_t *attr)
{
    /* 判断参数合法性 */
    if( NULL == attr )
    {
        log_error("function %s invalid parameter\n", __func__);
        return -1;
    }

    struct termios      new_termios;
    char                baudrate[32]={0};

    memset(&new_termios, 0, sizeof(struct termios));
    memset(&(attr->original_termios), 0, sizeof(struct termios));

    /* 获取串口原始属性，并保存到original_termios */
    if( tcgetattr(attr->fd, &(attr->original_termios)) < 0 )
    {
        log_error("function: %s get original termios to original termios failure: %s.\n", __func__, strerror(errno));
        return -2;
    }

    /* 获取串口原始属性，并保存到new_termios */
    if( tcgetattr(attr->fd, &new_termios) < 0 )
    {
        log_error("function: %s get original termios to new_termios termios failure: %s.\n", __func__, strerror(errno));
        return -3;
    }

    /* c_cflag: 控制模式标志，指定终端硬件的控制信息 */
    new_termios.c_cflag |= CLOCAL;  //忽略调制解调器线路状态
    new_termios.c_cflag |= CREAD;   //启动接收器
    new_termios.c_cflag &= ~CSIZE;  //CSIZE字符大小掩码，将与设置databits相关的标致位置零
    new_termios.c_lflag &= ~(ICANON | ECHO | ECHOE | ISIG);
    /* 
     * ICANON: 标准模式
     * ECHO: 回显所输入的字符
     * ECHOE: 如果同时设置了ICANON标志，ERASE字符删除前一个所输入的字符，WERASE删除前一个输入的单词
     * ISIG: 当接收到INTR/QUIT/SUSP/DSUSP字符，生成一个相应的信号
     */

    /* c_iflag: 输入模式标志，控制终端的输入方式 */
    new_termios.c_iflag &= ~(BRKINT | ICRNL | INPCK | ISTRIP | IXON);
    /* 
     * BRKINT: BREAK将会丢弃输入和输出队列中的数据(flush)，并且如果终端为前台进程组的控制终端，则BREAK将会产生一个SIGINT信号发送到这个前台进程组
     * ICRNL: 将输入中的CR转换为NL
     * INPCK: 允许奇偶校验
     * ISTRIP: 剥离第8个bits
     * IXON: 允许输出端的XON/XOF流控
     */
 
    /* c_oflag: 输出模式标志，控制终端的输出方式 */
    new_termios.c_oflag &= ~(OPOST);  //OPOST: 表示处理后输出，按照原始数据输出

    /* 设置输入输出波特率 */
    if(attr->baudrate)
    {
        sprintf(baudrate, "B%d", attr->baudrate);
        cfsetispeed(&new_termios, (int)baudrate);
        cfsetospeed(&new_termios, (int)baudrate);
    }
    else
    {
        cfsetispeed(&new_termios, B115200);
        cfsetospeed(&new_termios, B115200);
    }

    /* 设置数据位 */
    switch(attr->databits)
    {
        case '5':
            new_termios.c_cflag |= CS5;
            break;

        case '6':
            new_termios.c_cflag |= CS6;
            break;

        case '7':
            new_termios.c_cflag |= CS7;
            break;

        case '8':
            new_termios.c_cflag |= CS8;
            break;
        default:
            new_termios.c_cflag |= CS8;  //默认数据位为8
            break;
    }

    /* 设置校验方式 */
    switch(attr->parity)
    {
        /* 无校验 */
        case 'n':
        case 'N':
            new_termios.c_cflag &= ~PARENB;
            new_termios.c_iflag &= ~INPCK;
            break;

        /* 偶校验 even*/
        case 'e':
        case 'E':
            new_termios.c_cflag |= PARENB;
            new_termios.c_cflag &= ~PARODD;
            new_termios.c_iflag |= INPCK;
            break;

        /* 奇校验 odd*/
        case 'o':
        case 'O':
            new_termios.c_cflag |= PARENB;
            new_termios.c_cflag |= PARODD;
            new_termios.c_iflag |= INPCK;

        /* 设置为空格 */
        case 's':
        case 'S':
            new_termios.c_cflag &= ~PARENB;
            new_termios.c_cflag &= ~CSTOPB;

        /* 默认无校验 */
        default:
            new_termios.c_cflag &= ~PARENB;
            new_termios.c_iflag &= ~INPCK;
            break;
    }

    /* 设置停止位 */
    switch(attr->stopbits)
    {
        case '1':
            new_termios.c_cflag &= ~CSTOPB;
            break;
        case '2':
            new_termios.c_cflag |= CSTOPB;
            break;
        default:
            new_termios.c_cflag &= ~CSTOPB;
            break;
    }

    new_termios.c_cc[VMIN] = 0;  //最小接收字符数
    new_termios.c_cc[VTIME] = 0; //最长等待时间
    
    /* 设置最长的发送命令长度 */
    attr->max_send_len = 128;

    /* 清空串口输入输出缓冲区 */
    if( tcflush(attr->fd, TCIOFLUSH) < 0 )
    {
        log_error("function: %s tcflash failure: %s.\n", __func__, strerror(errno));
        return -4;
    }

    /* 设置串口属性 */
    if( tcsetattr(attr->fd, TCSANOW, &new_termios) < 0 )
    {
        log_error("function: %s set new_termios failure: %s.\n", __func__, strerror(errno));
        return -5;
    }

    return 0;
}

int serial_send(attr_ctx_t *attr, char *sbuf, int sbuf_len)
{
    if( !attr || !sbuf || sbuf_len <= 0 )
    {
        log_error("function %s invalid parameter\n", __func__);
        return -1;
    }

    int         rv = 0;
    int         rv_all = 0;
    char       *start = NULL;
    char       *end = NULL;

    if( sbuf_len > (attr->max_send_len) )
    {
        start = sbuf;
        end  = sbuf + sbuf_len;

        while(end > start)
        {
            if( (end - start) < attr->max_send_len )
            {
                rv = write(attr->fd, start, (end - start));
                if( rv < 0 || rv != (end - start))
                {
                    log_error("function: %s send last part AT to serial[%d] failure: %s.\n", __func__, attr->fd, strerror(errno));
                    return -2;
                }

                start += (end - start);

                rv_all += rv;
                rv = rv_all;
            }
            else
            {
                rv = write(attr->fd, start, attr->max_send_len);
                if( rv < 0 || rv != attr->max_send_len )
                {
                    log_error("function: %s send front part AT to serial[%d] failure: %s.\n", __func__, attr->fd, strerror(errno));
                    return -3;
                }
            
                start += attr->max_send_len;

                rv_all += rv;
            }
        }
    }
    else
    {
        rv = write(attr->fd, sbuf, sbuf_len);
        if( rv <= 0 || rv != sbuf_len)
        {
            log_error("function: %s send to serial[%d] failure: %s.\n", __func__, attr->fd, strerror(errno));
            return -4;
        }
    }
    
    return rv;
}

int serial_recv(attr_ctx_t *attr, char *rbuf, int rbuf_size, int timeout)
{
    if( !attr || !rbuf || rbuf_size <= 0 )
    {
        log_error("function: %s invalid parameter.\n", __func__);
        return -1;
    }

    int                   rv = -1;
    fd_set                rset;
    struct timeval        st_timeout;

    st_timeout.tv_sec = (time_t)(timeout);
    st_timeout.tv_usec = 0;
    FD_ZERO(&rset);
    FD_SET(attr->fd, &rset);
    
    rv = select(attr->fd+1, &rset, NULL, NULL, &st_timeout);
    if(rv < 0)
    {
        log_error("function %s select failed: %s\n", __func__, strerror(errno));
        return -2;
    }
    else if(0 == rv)
    {
        log_info("function %s listen select recv time out.\n", __func__);
        return 0;
    }
    else
    {
        usleep(1000);
        rv = read(attr->fd, rbuf, rbuf_size);
        if( rv <= 0 )
        {
            log_error("function %s read from serial[%d] failure: %s.\n", __func__, attr->fd, strerror(errno));
            return -3;
        }
    }

    return rv;
}